/**
 * @file glvector.h
 * @ingroup Utilities
 *
 * @brief glvector template definition
 */

/* --
 * Copyright (c) 2012, Christophe Huriaux
 * Distributed under the terms of the MIT license.
 * http://www.opensource.org/licenses/mit-license.php
 * --
 */

#ifndef GLVECTOR_H
#define GLVECTOR_H

#include <initializer_list>
#include <iostream>
#include <cmath>

//! A macro used in glvector template for element-wide iterations
#define _glvector_each(i) for (int i = 0; i < N; i++)

template<typename Tx>
class glvector3;

template<typename Tx>
class glvector2;

/** @class glvector<N,T>
 *  @ingroup Utilities
 *  @brief Generic vector class implementation
 *
 *    @c glvector<N,T> implements a generic vector class type supporting the various
 * possible arithmetic operations on vectors.
 *    Specialization of this template includes @c glvector2 and @c glvector3
 * for, respectively, 2 elements and 3 elements vectors.
 *
 * @tparam N Size of the vector (number of coefficients)
 * @tparam T Type of the elements
 */
template<int N, typename T = double>
class glvector
{
protected:
    /** @var _e
     *  @brief Vector array
     *
     *    @c _e stores the various coefficients of the vector.
     */
	T _e[N];

public:
	/** @name Constructors
	 * @{
	 */

	/**
	 * @brief Empty constructor
	 *
	 *    The empty constructor allows to create an empty vector in which all
	 * the elements are initialized to 0.
	 */
	glvector()
	{
		_glvector_each(i) _e[i] = 0;
	}


	/**
	 * @brief Copy constructor
	 *
	 *    Initializes the elements of the vector by copying those of the given
	 * vector reference, thus making an effective copy of the vector @a s.
	 *
	 * @param s Reference to the source vector to copy
	 */
	glvector(const glvector<N, T>& s)
	{
		_glvector_each(i) _e[i] = s._e[i];
	}


	/**
	 * @brief Distance constructor
	 *
	 *    Initializes the current vector as a difference between the @a dest
	 * and the @a src vectors.
	 *    If @a src and @a dest are considered like two points in R^n, the
	 * resulting @c glvector is effectively a vector between these two points.
	 *
	 * @param src Source vector
	 * @param dest Destination vector
	 */
	glvector(const glvector<N, T>& src, const glvector<N, T>& dest)
	{
		_glvector_each(i) _e[i] = dest._e[i] - src._e[i];
	}


	/**
	 * @brief Initializer list constructor
	 *
	 *    Initializes the vectors elements from an initializer list, just like
	 * if it was an array.
	 *
	 * @param l Initializer list
	 *
     * @warning It's a C++11 feature
	 * @todo The code doesn't check if there is enough elements to fill the
	 * internal array.
	 */
	glvector(std::initializer_list<T> l)
	{
		typename std::initializer_list<T>::const_iterator it, iend;
		unsigned int i;

		for (it = l.begin(), iend = l.end(), i=0; it != iend && i < N; it++, i++)
			_e[i] = *it;
	}
	//!@}


	/**
	 * @brief Assigment operator
	 *
	 *    Re-initializes all of the vector elements by copying those of the
	 * given source vector.
	 *
	 * @param s Source vector
	 * @return A reference to the current vector
	 */
	glvector<N, T>& operator=(const glvector<N, T>& s)
	{
		_glvector_each(i) _e[i] = s._e[i];
		return *this;
	}


	/** @name Arithmetic operators
	 * @{
	 */

    /**
     * @brief Opposite operator
     *
     *    Returns the opposite vector v = -v.
     *
     * @return The opposite of the current vector
     */
	glvector<N, T> operator-() const
	{
		glvector<N, T> v;
		_glvector_each(i) v._e[i] = -_e[i];
		return v;
	}


    /**
     * @brief Addition operator
     *
     *    Adds the source vector to the current one.
     *
     * @param source Source vector
     * @return A vector containing the result of the operation
     */
	glvector<N, T> operator+(const glvector<N, T>& source) const
	{
		glvector<N, T> v;
		_glvector_each(i) v._e[i] = _e[i] + source._e[i];
		return v;
	}


    /**
     * @brief Substraction operator
     *
     *    Substract the source vector from the current one.
     *
     * @param source Source vector
     * @return A vector containing the result of the operation
     */
	glvector<N, T> operator-(const glvector<N, T>& source) const
	{
		glvector<N, T> v;
		_glvector_each(i) v._e[i] = _e[i] - source._e[i];
		return v;
	}


    /**
     * @brief Multiplication operator
     *
     *    Multiply the current vector by @a a.
     *
     * @param a Multiplier.
     * @return A vector containing the result of the operation
     */
	glvector<N, T> operator*(const double a) const
	{
		glvector<N, T> v;
		_glvector_each(i) v._e[i] = _e[i] * a;
		return v;
	}


    /**
     * @brief Division operator
     *
     *    Divides the current vector by @a a.
     *
     * @param a Divisor.
     * @return A vector containing the result of the operation
     */
	glvector<N, T> operator/(const double a) const
	{
		glvector<N, T> v;
		_glvector_each(i) v._e[i] = _e[i] / a;
		return v;
	}
	//!@}


	/** @name Vector operations
	 * @{
	 */

    /**
     * @brief Cross-product operator
     *
     *    Performs a cross-product between the vector @a right and the
     * current one.
     *
     * @param right Right vector operand
     * @return A vector containing the result of the operation
     *
     * @warning Due to its complexity this operation is only implemented in
     * specialized classes.
     */
	glvector<N, T> operator^(const glvector<N, T>& right) const
	{
		glvector<N, T> v;
		return v;
	}


    /**
     * @brief Dot-product operator
     *
     *    Performs a dot-product between the vector @a right and the
     * current one. The results of the dot-product is typically the
     * sum of the N terms multiplied one by one.
     *
     * @param right Right vector operand
     * @return The scalar result of the dot-product
     */
	double operator*(const glvector<N, T>& right) const
	{
		double sum = 0;
		_glvector_each(i) sum += _e[i] * right._e[i];
		return sum;
	}
	//!@}


	/** @name Compound assigment operators
	 * @{
	 */

	/**
	 * @brief Addition-assigment operator
	 *
	 *    Adds the @a right vector to the current one.
	 *
	 * @param right Right operand
	 * @return A reference to the current vector after the operation
	 */
	glvector<N, T>& operator+=(const glvector<N, T>& right)
	{
		*this = *this + right;
		return *this;
	}


    /**
     * @brief Substraction-assigment operator
     *
     *    Substract the @a right vector from the current one.
     *
     * @param right Right operand
     * @return A reference to the current vector after the operation
     */
	glvector<N, T>& operator-=(const glvector<N, T>& right)
	{
		*this = *this - right;
		return *this;
	}


    /**
     * @brief Multiplication-assigment operator
     *
     *    Multiply the current vector by @a a.
     *
     * @param a Multiplier
     * @return A reference to the current vector after the operation
     */
	glvector<N, T>& operator*=(const double a)
	{
		*this = *this * a;
		return *this;
	}



    /**
     * @brief Division-assigment operator
     *
     *    Divides the current vector by @a a.
     *
     * @param a Divisor
     * @return A reference to the current vector after the operation
     */
	glvector<N, T>& operator/=(const double a)
	{
		*this = *this / a;
		return *this;
	}
    //!@}


	/** @name Vector metrics
	 * @{
	 */

	/**
	 * @brief Calculate the vector norm
	 *
	 *    Performs the squared sum of all terms and returns the vector norm,
	 * <em>i.e.</em> his length.
	 *
	 * @return Vector norm (length)
	 */
	double norm() const
	{
		double n = 0;
		_glvector_each(i) n += _e[i] * _e[i];

		return std::sqrt(n);
	}

	/**
	 * @brief Vector normalization
	 *
	 *    Normalizes the vector, after this operation the vector have a norm
	 * of 1.
	 *
	 * @return A reference to the current vector after the operation
	 */
	glvector<N, T>& normalize()
	{
		*this /= norm();
		return *this;
	}
	//!@}


	/** @name Comparison operations
	 * @{
	 */

	/**
	 * @brief Equality test
	 *
	 *    Test every single vector coefficient and check if they are two-by-two
	 * equals.
	 *
	 * @param src The vector to test against
	 *
	 * @retval true If the two vectors matches exactly
	 * @retval false If they don't
	 */
	bool operator==(const glvector<N, T>& src) const
	{
		_glvector_each(i) if (_e[i] != src._e[i]) return false;
		return true;
	}
	//!@}


	/** @name Element access
	 * @{
	 */

	/**
	 * @brief Subscript operator
	 *
	 *   Returns a reference to to i-th element of the vector.
	 *
	 * @param i Coefficient number
	 * @return Reference to the given element
	 */
	T& operator[](unsigned int i)
	{
		return _e[i];
	}

    /**
     * @brief Const subscript operator
     *
     *   Returns a const reference to to i-th element of the vector.
     *
     * @param i Coefficient number
     * @return Const reference to the given element
     */
	const T& operator[](unsigned int i) const
	{
		return _e[i];
	}
	//!@}

	template<int _N, typename _T>
	friend std::ostream& operator<<(std::ostream& out, const glvector<_N, _T>& v);

	friend class glvector3<T>;
    friend class glvector2<T>;
};


/**
 * @relates glvector
 * @brief std::ostream stream insertion formatting
 *
 *    Allows the vector to be directly insert in a standard @c std::ostream
 * stream and to be prettily displayed.
 *
 * @param out Stream
 * @param v Vector
 *
 * @tparam _N Size of the vector
 * @tparam _T Type of the vector elements
 * @return Reference to the stream
 *
 */
template<int N, typename T>
std::ostream& operator<<(std::ostream& out, const glvector<N, T>& v)
{
	out << "(";

	for (int i = 0; i < N-1; i++)
		out << v._e[i] << ";";

	out << v._e[N-1] << ")";
	return out;
}



/** @class glvector3<Tx>
 *  @ingroup Utilities
 *  @brief Specialized 3-elements vector class
 *
 *    @c glvector3<Tx> implements a 3-elements vector type. It specializes
 * @c glvector<N,T> to be able to construct a @c glvector object directly
 * with the N coefficients.
 *
 * @tparam Tx Type of the elements
 */
template<typename Tx>
class glvector3 : public glvector<3, Tx>
{

public:
    /** @name Constructors
     * @{
     */


    /**
     * @brief Empty constructor
     *
     *    The empty constructor allows to create an empty vector in which all
     * the elements are initialized to 0.
     */
	glvector3()
	{
		glvector<3, Tx>::_e[0] = 0;
		glvector<3, Tx>::_e[1] = 0;
		glvector<3, Tx>::_e[2] = 0;
	}


    /**
     * @brief Copy constructor
     *
     *    Initializes the elements of the vector by copying those of the given
     * vector reference, thus making an effective copy of the vector @a s.
     *
     * @param s Reference to the source vector to copy
     */
	glvector3(const glvector3<Tx>& s)
	{
		glvector<3, Tx>::_e[0] = s._e[0];
		glvector<3, Tx>::_e[1] = s._e[1];
		glvector<3, Tx>::_e[2] = s._e[2];
	}


    /**
     * @brief Copy constructor from non-specialized template
     *
     *    Initializes the elements of the vector by copying those of the given
     * vector reference, thus making an effective copy of the vector @a s.
     *
     * @param s Reference to the source vector to copy
     */
	glvector3(const glvector<3, Tx>& s)
	{
		glvector<3, Tx>::_e[0] = s._e[0];
		glvector<3, Tx>::_e[1] = s._e[1];
		glvector<3, Tx>::_e[2] = s._e[2];
	}


    /**
     * @brief Distance constructor
     *
     *    Initializes the current vector as a difference between the @a dest
     * and the @a src vectors.
     *    If @a src and @a dest are considered like two points in R^n, the
     * resulting @c glvector is effectively a vector between these two points.
     *
     * @param src Source vector
     * @param dest Destination vector
     */
	glvector3(const glvector3<Tx>& src, const glvector3<Tx>& dest)
	{
		glvector<3, Tx>::_e[0] = dest._e[0] - src._e[0];
		glvector<3, Tx>::_e[1] = dest._e[1] - src._e[0];
		glvector<3, Tx>::_e[2] = dest._e[2] - src._e[0];
	}


    /**
     * @brief 3-elements constructor
     *
     *    Initializes the current vector with the 3 elements passed to the
     * constructor @a x, @a y and @a z.
     *
     * @param x 1st element
     * @param y 2nd element
     * @param z 3rd element
     */
	glvector3(Tx x, Tx y, Tx z)
	{
		glvector<3, Tx>::_e[0] = x;
		glvector<3, Tx>::_e[1] = y;
		glvector<3, Tx>::_e[2] = z;
	}
    //!@}


    /** @name Vector operations
     * @{
     */

    /**
     * @brief Cross-product operator
     *
     *    Performs a cross-product between the vector @a right and the
     * current one.
     *
     * @param right Right vector operand
     * @return A vector containing the result of the operation
     */
	glvector3<Tx> operator^(const glvector3<Tx>& s) const // Cross product
	{
		glvector3<Tx> r;

		r._e[0] = glvector<3, Tx>::_e[1]*s._e[2] - glvector<3, Tx>::_e[2]*s._e[1];
		r._e[1] = glvector<3, Tx>::_e[2]*s._e[0] - glvector<3, Tx>::_e[0]*s._e[2];
		r._e[2] = glvector<3, Tx>::_e[0]*s._e[1] - glvector<3, Tx>::_e[1]*s._e[0];
		return r;
	}
	//!@}
};



/** @class glvector2<Tx>
 *  @ingroup Utilities
 *  @brief Specialized 2-elements vector class
 *
 *    @c glvector3<Tx> implements a 2-elements vector type. It specializes
 * @c glvector<N,T> to be able to construct a @c glvector object directly
 * with the N coefficients.
 *
 * @tparam Tx Type of the elements
 */
template<typename Tx>
class glvector2 : public glvector<2, Tx>
{

public:
    /** @name Constructors
     * @{
     */


    /**
     * @brief Empty constructor
     *
     *    The empty constructor allows to create an empty vector in which all
     * the elements are initialized to 0.
     */
	glvector2()
	{
		glvector<2, Tx>::_e[0] = 0;
		glvector<2, Tx>::_e[1] = 0;
	}


    /**
     * @brief Copy constructor
     *
     *    Initializes the elements of the vector by copying those of the given
     * vector reference, thus making an effective copy of the vector @a s.
     *
     * @param s Reference to the source vector to copy
     */
	glvector2(const glvector2<Tx>& s)
	{
		glvector<2, Tx>::_e[0] = s._e[0];
		glvector<2, Tx>::_e[1] = s._e[1];
	}


    /**
     * @brief Copy constructor from non-specialized template
     *
     *    Initializes the elements of the vector by copying those of the given
     * vector reference, thus making an effective copy of the vector @a s.
     *
     * @param s Reference to the source vector to copy
     */
	glvector2(const glvector<2, Tx>& s)
	{
		glvector<2, Tx>::_e[0] = s._e[0];
		glvector<2, Tx>::_e[1] = s._e[1];
	}


    /**
     * @brief Distance constructor
     *
     *    Initializes the current vector as a difference between the @a dest
     * and the @a src vectors.
     *    If @a src and @a dest are considered like two points in R^n, the
     * resulting @c glvector is effectively a vector between these two points.
     *
     * @param src Source vector
     * @param dest Destination vector
     */
	glvector2(const glvector2<Tx>& src, const glvector2<Tx>& dest)
	{
		glvector<2, Tx>::_e[0] = dest._e[0] - src._e[0];
		glvector<2, Tx>::_e[1] = dest._e[1] - src._e[0];
	}


    /**
     * @brief 2-elements constructor
     *
     *    Initializes the current vector with the 2 elements passed to the
     * constructor @a x and @a y.
     *
     * @param x 1st element
     * @param y 2nd element
     */
	glvector2(Tx x, Tx y)
	{
		glvector<2, Tx>::_e[0] = x;
		glvector<2, Tx>::_e[1] = y;
	}
	//!@}


    /** @name Vector operations
     * @{
     */

    /**
     * @brief Cross-product operator
     *
     *    Performs a cross-product between the vector @a right and the
     * current one.
     *
     * @param right Right vector operand
     * @return A vector containing the result of the operation
     */
	glvector2<Tx> operator^(const glvector2<Tx>& s) const // Cross product
	{
		glvector3<Tx> r;

		r._e[0] = glvector<2, Tx>::_e[1]*s._e[2] - glvector<2, Tx>::_e[2]*s._e[1];
		r._e[1] = glvector<2, Tx>::_e[2]*s._e[0] - glvector<2, Tx>::_e[0]*s._e[2];
		return r;
	}
	//!@}
};

/** @typedef vector3d
 * @ingroup Utilities
 * @relates glvector3
 * @brief 3-dimensional vector with double coefficients.
 */
typedef glvector3<double> vector3d;

/** @typedef vector2d
 * @ingroup Utilities
 * @relates glvector2
 * @brief 2-dimensional vector with double coefficients.
 */
typedef glvector2<double> vector2d;


/** @typedef vector3f
 * @ingroup Utilities
 * @relates glvector3
 * @brief 3-dimensional vector with float coefficients.
 */
typedef glvector3<float> vector3f;

/** @typedef vector2f
 * @ingroup Utilities
 * @relates glvecto2
 * @brief 2-dimensional vector with float coefficients.
 */
typedef glvector2<float> vector2f;
#endif // GLVECTOR_H
